﻿using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

namespace Voodoo.SharePoint.ReadUnreadMarks
{
    /// <summary>
    /// thread safe queue for document locators 
    /// to prevent thrashing we only allow one
    /// locator of each type in the queue at a time
    /// </summary>
    internal class BackgroundDocumentMarkupQueue
    {
        const int THRASHING_DETECT_IN_SECONDS = 5;
        bool _QueueIsOpen = false;
        object _LockProxy = new object();
        Queue<DocumentItemLocator> _Queue;
        List<string> _Keys;
        Dictionary<string,DateTime> _RecentKeys;

        internal BackgroundDocumentMarkupQueue()
        {
            _QueueIsOpen = true;
            _Queue = new Queue<DocumentItemLocator>();
            _Keys = new List<string>();
            _RecentKeys = new Dictionary<string, DateTime>();
        }


         ~BackgroundDocumentMarkupQueue()
        {
            Close();
        }

        internal void Close()
        {
            lock (_LockProxy)
            {
                _QueueIsOpen = false;
                _Queue.Clear();
                Monitor.PulseAll(_LockProxy);
            }
        }

        public DocumentItemLocator Dequeue()
        {
            lock (_LockProxy)
            {
                while (_QueueIsOpen && (_Queue.Count == 0))
                {
                    if (!Monitor.Wait(_LockProxy, Timeout.Infinite))
                    {
                        throw new QueueException("Unknown queue exception");
                    }
                }
                if (_QueueIsOpen)
                {
                    DocumentItemLocator returnValue = _Queue.Dequeue();
                    _Keys.Remove(returnValue.Key);
                    return returnValue;
                }
                else
                {
                    throw new QueueClosedException("Queue has been deactivated");
                }
            }
        }

        public void Enqueue(DocumentItemLocator enqueueThis)
        {
            lock (_LockProxy)
            {
                string queueKey = enqueueThis.Key;
                if (OkToEnqueue(queueKey))
                {
                    _Keys.Add(queueKey);
                    _RecentKeys.Add(queueKey, DateTime.Now);
                    _Queue.Enqueue(enqueueThis);
                    Monitor.Pulse(_LockProxy);
                }
            }
        }

        bool OkToEnqueue(string queueKey)
        {
            if (_RecentKeys.ContainsKey(queueKey))
            {
                //if the item is in the recent queue, check
                //to see if the queue time has expired
                DateTime lastEnqueue = _RecentKeys[queueKey];
                if (lastEnqueue.AddSeconds(THRASHING_DETECT_IN_SECONDS) < DateTime.Now)
                {
                    _RecentKeys.Remove(queueKey);
                }
                else
                {
                    //too close together so do not queue
                    return false;
                }
            }
            //not a "recent" item so make sure
            //it is not in the queue
            return !_Keys.Contains(queueKey);
        }
    }

    #region exceptions
    public class QueueClosedException : System.ApplicationException
    {
        public QueueClosedException()
            : base()
        {
        }
        public QueueClosedException(string errorMessage)
            : base(errorMessage)
        {
        }
        public QueueClosedException(string errorMessage, Exception innerErr)
            : base(errorMessage, innerErr)
        {
        }
    }

    public class QueueException : System.ApplicationException
    {
        public QueueException()
            : base()
        {
        }
        public QueueException(string errorMessage)
            : base(errorMessage)
        {
        }
        public QueueException(string errorMessage, Exception innerErr)
            : base(errorMessage, innerErr)
        {
        }
    }
    #endregion
}
